/* caLog.c */

/*
 * Licensed to the ConArtist Development Team (CADT).
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The CADT licenses this file to you under the Apache License, 
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>

#include <apr_strings.h>
#include <apr_file_io.h>
#include <apr_thread_rwlock.h>

#include "conartist.h"
#include "caStore.h"
#define LOG_NAMES
#include "caLog.h"
#include "caGenerator.h"

#include "config.h"
#include "caPrivate.h"

static struct caLogFacility *logs = NULL;
static int maxLevel = 0;
static apr_thread_rwlock_t *rwLock = NULL;

static char *logLevels[] = {
    "Unknown", "FATAL", "ERROR", "WARN ", "INFO ", "DEBUG"
};

#ifdef HAVE_SYSLOG_H
#define SYSLOG_NAMES
#include <syslog.h>

apr_status_t logInit(void)
{
    return apr_thread_rwlock_create(&rwLock, globalPool);
}

static int syslogLogLevel(int calvl)
{
    switch(calvl) {
        case CA_LOG_FATAL:   return LOG_CRIT;
        case CA_LOG_ERROR:   return LOG_ERR;
        case CA_LOG_WARN:    return LOG_WARNING;
        case CA_LOG_INFO:    return LOG_INFO;
        case CA_LOG_DEBUG:   return LOG_DEBUG;
    }
    return CA_LOG_WARN;
}

static apr_status_t syslogWrite(void *data, int lvl, const char *msg)
{
    int slvl = syslogLogLevel(lvl);
    syslog(slvl, msg);
    return APR_SUCCESS;
}

static void syslogClose(void *data)
{
    closelog();
}
#endif

/* This is always defined, but in the absence of syslog functionality
 * it becomes a no-op.
 */
struct caLogFacility *caOpenSyslog(const char *f, const char *name)
{
    struct caLogFacility *lf = NULL;
    int logf = -1, i;

    if (!f)
        return NULL;

#ifdef HAVE_SYSLOG_H
    for(i = 0; facilitynames[i].c_name; i++) {
        if (strcasecmp(facilitynames[i].c_name, f) == 0) {
            logf = facilitynames[i].c_val;
            break;
        }
    }
    if (logf == -1)
        return NULL;

    openlog("ConArtist", LOG_PID, logf);

    lf = apr_pcalloc(globalPool, sizeof(*lf));
    if (!lf)
        return NULL;
    lf->name = (char *)apr_pstrdup(globalPool, name);
    lf->level = CA_LOG_DEBUG; /* TODO - make this configurable */
    lf->write = &syslogWrite;
    lf->close = &syslogClose;

#endif
    return lf;
}

/* File Logging */

static apr_status_t logFileWrite(void *data, int lvl, const char *msg)
{
    apr_file_t *f = (apr_file_t *)data;
    apr_size_t wr = strlen(msg);
    int needCR = msg[wr - 1] == '\n' ? 0 : 1;
    char dateStr[APR_RFC822_DATE_LEN];
    apr_time_t now;
    apr_time_exp_t expTime;
    apr_size_t dsLength = 0;

    /* Create time string to prepend to output
     * TODO - make format configurable!
     */
    now = apr_time_now();
    apr_time_exp_gmt(&expTime, now);
    apr_strftime(dateStr, &dsLength, APR_RFC822_DATE_LEN,
                 "%Y%m%dT%R:%S", &expTime);

    apr_file_printf(f, "%s [%s] ", dateStr, logLevels[lvl]);
    if (apr_file_write(f, msg, &wr) == APR_SUCCESS) {
        if (wr == strlen(msg)) {
            /* As it's a file we're writing to, make sure we have
             * a CR as otherwise the output gets funky real fast.
             */
            if (needCR)
                apr_file_printf(f, "\n");
            return APR_SUCCESS;
        }
    }
    return APR_EINVAL;
}

static void logFileClose(void *data)
{
    apr_file_t *f = (apr_file_t *)data;
    apr_file_close(f);
}

struct caLogFacility *caOpenLogFile(const char *path, const char *name)
{
    struct caLogFacility *lf = NULL;
    apr_file_t *f = NULL;
    apr_int32_t flags = APR_READ | APR_WRITE | APR_CREATE | APR_APPEND;
    apr_fileperms_t perms = APR_FPROT_UREAD | APR_FPROT_UWRITE;
    apr_status_t rv;

    if (!path)
        return NULL;

    rv = apr_file_open(&f, path, flags, perms, globalPool);
    if (rv != APR_SUCCESS) {
        caLog(CA_LOG_ERROR, "Unable to open '%s'\n", path);
        return NULL;
    }

    lf = apr_pcalloc(globalPool, sizeof(*lf));
    if (!lf)
        return NULL;
    lf->name = (char *)apr_pstrdup(globalPool, name);
    lf->level = CA_LOG_DEBUG; /* TODO - make this configurable */
    lf->data = f;
    lf->write = &logFileWrite;
    lf->close = &logFileClose;

    return lf;
}
    
void caLogClose(struct caLogFacility *f)
{
    f->close(f->data);
}

#define BUFLEN   1024
void caLog(int lvl, const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    cavLog(lvl, fmt, args);
    va_end(args);
}

void cavLog(int lvl, const char *fmt, va_list args)
{
    struct caLogFacility *l;
    char *buffer;
    apr_status_t rv;

    if (!logs || lvl > maxLevel) {
        apr_size_t wr = strlen(fmt);
        int needCR = fmt[wr - 1] == '\n' ? 0 : 1;
        /* print to stderr */
        vfprintf(stderr, fmt, args);
        va_end(args);
        if (needCR)
            fprintf(stderr, "\n");
        return;
    }

    buffer = apr_pvsprintf(globalPool, fmt, args);

    rv = apr_thread_rwlock_rdlock(rwLock);
    if (rv == APR_SUCCESS) {
        for (l = logs; l; l = l->next) {
            if (l->write && l->level >= lvl)
                l->write(l->data, lvl, buffer);
        }
        apr_thread_rwlock_unlock(rwLock);
    }
}

apr_status_t addLogFacility(struct caLogFacility *lf)
{
    struct caLogFacility *l;
    apr_status_t rv;

    if (!lf)
        return APR_EINVAL;

    for (l = logs; l; l = l->next) {
        if (strcasecmp(l->name, lf->name) == 0)
            return EALREADY;
    }

    rv = apr_thread_rwlock_wrlock(rwLock);    
    if (rv == APR_SUCCESS) {
        if (logs) {
            for (l = logs; l->next; l = l->next)
                ;
            l->next = lf;
        } else
            logs = lf;
        apr_thread_rwlock_unlock(rwLock);
    } else
        return rv;

    if (lf->level > maxLevel)
        maxLevel = lf->level;

    return APR_SUCCESS;
}

int caLogLevelString(const char *lvl)
{
    const char *dfltLvl = NULL;
    int i;

    for (i = 1; levelNames[i]; i++) {
        if (strcasecmp(lvl, levelNames[i]) == 0)
            return i;
    }
    dfltLvl = apr_table_get(libConfig, "DEFAULT_LOG_LEVEL");
    return (atoi(dfltLvl));
}
